跳到主要内容

填充和步幅

import tensorflow as tf
import numpy as np

在上一节的例子里,我们使用高和宽为3的输入与高和宽为2的卷积核得到高和宽为2的输出。一般来说,假设输入形状是nh×nwn_h\times n_w,卷积核窗口形状是kh×kwk_h\times k_w,那么输出形状将会是

(nhkh+1)×(nwkw+1).(n_h-k_h+1) \times (n_w-k_w+1).

所以卷积层的输出形状由输入形状和卷积核窗口形状决定。本节我们将介绍卷积层的两个超参数,即填充和步幅。它们可以对给定形状的输入和卷积核改变输出形状。

填充(padding)

填充(padding)是指在输入高和宽的两侧填充元素(通常是0元素)。图5.2里我们在原输入高和宽的两侧分别添加了值为0的元素,使得输入高和宽从3变成了5,并导致输出高和宽由2增加到4。图5.2中的阴影部分为第一个输出元素及其计算所使用的输入和核数组元素:0×0+0×1+0×2+0×3=00\times0+0\times1+0\times2+0\times3=0

img

一般来说,如果在高的两侧一共填充php_h行,在宽的两侧一共填充pwp_w列,那么输出形状将会是

(nhkh+ph+1)×(nwkw+pw+1)(n_h-k_h+p_h+1)\times(n_w-k_w+p_w+1)

也就是说,输出的高和宽会分别增加php_hpwp_w

在很多情况下,我们会设置ph=kh1p_h=k_h-1pw=kw1p_w=k_w-1来使输入和输出具有相同的高和宽。这样会方便在构造网络时推测每个层的输出形状。假设这里khk_h是奇数,我们会在高的两侧分别填充ph/2p_h/2行。如果khk_h是偶数,一种可能是在输入的顶端一侧填充ph/2\lceil p_h/2\rceil行,而在底端一侧填充ph/2\lfloor p_h/2\rfloor行。在宽的两侧填充同理。

卷积神经网络经常使用奇数高宽的卷积核,如1、3、5和7,所以两端上的填充个数相等。对任意的二维数组X,设它的第i行第j列的元素为X[i,j]。当两端上的填充个数相等,并使输入和输出具有相同的高和宽时,我们就知道输出Y[i,j]是由输入以X[i,j]为中心的窗口同卷积核进行互相关计算得到的。

下面的例子里我们创建一个高和宽为3的二维卷积层,然后设输入高和宽两侧的填充数分别为1。给定一个高和宽为8的输入,我们发现输出的高和宽也是8。

def comp_conv2d(conv2d, X):
X = tf.reshape(X,(1,) + X.shape + (1,))
Y = conv2d(X)
#input_shape = (samples, rows, cols, channels)
return tf.reshape(Y,Y.shape[1:3])

conv2d = tf.keras.layers.Conv2D(1, kernel_size=3, padding='same')
X = tf.random.uniform(shape=(8,8))
comp_conv2d(conv2d,X).shape

输出:

TensorShape([8, 8])

步幅(strides)

在上一节里我们介绍了二维互相关运算。卷积窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。我们将每次滑动的行数和列数称为步幅(stride)。

目前我们看到的例子里,在高和宽两个方向上步幅均为1。我们也可以使用更大步幅。图5.3展示了在高上步幅为3、在宽上步幅为2的二维互相关运算。可以看到,输出第一列第二个元素时,卷积窗口向下滑动了3行,而在输出第一行第二个元素时卷积窗口向右滑动了2列。当卷积窗口在输入上再向右滑动2列时,由于输入元素无法填满窗口,无结果输出。图5.3中的阴影部分为输出元素及其计算所使用的输入和核数组元素:0×0+0×1+1×2+2×3=80\times0+0\times1+1\times2+2\times3=80×0+6×1+0×2+0×3=60\times0+6\times1+0\times2+0\times3=6

img

一般来说,当高上步幅为shs_h,宽上步幅为sws_w时,输出形状为

(nhkh+ph+sh)/sh×(nwkw+pw+sw)/sw\lfloor(n_h-k_h+p_h+s_h)/s_h\rfloor \times \lfloor(n_w-k_w+p_w+s_w)/s_w\rfloor

如果设置ph=kh1p_h=k_h-1pw=kw1p_w=k_w-1,那么输出形状将简化为(nh+sh1)/sh×(nw+sw1)/sw\lfloor(n_h+s_h-1)/s_h\rfloor \times \lfloor(n_w+s_w-1)/s_w\rfloor。更进一步,如果输入的高和宽能分别被高和宽上的步幅整除,那么输出形状将是(nh/sh)×(nw/sw)(n_h/s_h) \times (n_w/s_w)

下面我们令高和宽上的步幅均为2,从而使输入的高和宽减半。

conv2d = tf.keras.layers.Conv2D(1, kernel_size=3, padding='same',strides=2)
comp_conv2d(conv2d, X).shape

输出:

TensorShape([4, 4])

接下来是一个稍微复杂点儿的例子。

conv2d = tf.keras.layers.Conv2D(1, kernel_size=(3,5), padding='valid', strides=(3,4))
comp_conv2d(conv2d, X).shape

输出:

TensorShape([2, 1])

小结

  • 填充可以增加输出的高和宽。这常用来使输出与输入具有相同的高和宽。
  • 步幅可以减小输出的高和宽,例如输出的高和宽仅为输入的高和宽的1/n1/nnn为大于1的整数)。